package com.deli.wuziqi;


import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;

/**
 * @Auther: xudeli
 * @Date: 2018/12/6/006 27
 * @Description: 老铁互怼五子棋1.0
 */

//打开游戏选择"是"则为联机模式,选择"否"或关闭对话框则为单机模式,默认端口号9653(这是一个神奇数字)
//本版本联机需要输入对方主机名或IP地址,并且需要双方端口号对应相反
//本版本联机模式,双方轮流执黑
//如果主机名或IP为自己主机(或没有输入),则转为单机模式,此时需两个端口号一致
//为了解决线程异常问题(水平有限...),特推出此魔改版,打开游戏必须选择其中一种模式(联机或单机),要想更换,则要关闭游戏重新打开

public class NetLTWZQDemo extends JPanel implements MouseListener, Runnable {

    int x;// 落子处棋盘坐标
    int y;
    int[][] chess = new int[12][12];// 棋盘大小为12x12

    int ex;// 传送鼠标点击坐标
    int ey;
    boolean me = true;// 初始化为己方先落黑子
    boolean one = true;// 控制联机双方每次只能一个人下子
    static boolean start = false;// 默认打开游戏不能下子,需先选择游戏模式

    static InetAddress inetAddress;// 客户端确认服务端的IP地址
    static String UsernameOrIp;// 联机对方主机名或IP地址

    // 客户端和服务端端口号一致则为单机版五子棋
    static int clientPort;// 设置客户端端口
    static int serverPort;// 设置服务端端口
    static boolean danJi;// 选择单机模式,则程序只判断一次胜负,防止对局结束后无法落子,若联机模式,则判断两次胜负

    static Thread t;// 声明一个线程
    private static final long serialVersionUID = 1L;//emmm 没什么用,提示要有一个就放一个吧

    public static void main(String[] args) {
        JFrame jFrame = new JFrame("老铁互怼五子棋");
        jFrame.setBounds((Toolkit.getDefaultToolkit().getScreenSize().width - 655) / 2,
                (Toolkit.getDefaultToolkit().getScreenSize().height - 675) / 2, 655, 675);// 窗口屏幕正中放置
        jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jFrame.setResizable(false);//窗体大小固定
        jFrame.setVisible(true);//窗体设置可见
        NetLTWZQDemo netLTWZQDemo = new NetLTWZQDemo();
        jFrame.add(netLTWZQDemo);
        t = new Thread(netLTWZQDemo);

        //为了方便(又懒又笨..),直接把所有内容写到一个类里,且把模式选择对话框放在main方法里
        int respones = JOptionPane.showConfirmDialog(null, "老铁你愁啥,想找个人怼怼?", "生死看淡,不服就干!", JOptionPane.YES_NO_OPTION,
                JOptionPane.WARNING_MESSAGE);

        if (respones == JOptionPane.YES_OPTION) {
            try {
                UsernameOrIp = JOptionPane.showInputDialog(null, "输入丫主机名或IP", "记小本本上每天看两遍", JOptionPane.WARNING_MESSAGE)
                        .trim();
                clientPort = Integer.parseInt(JOptionPane
                        .showInputDialog(null, "老铁从几号门出发?", "门牌号:1024~65535", JOptionPane.WARNING_MESSAGE)
                        .trim());
                serverPort = Integer.parseInt(JOptionPane
                        .showInputDialog(null, "对面在几号门?", "门牌号:1024~65535", JOptionPane.WARNING_MESSAGE)
                        .trim());
            } catch (Exception e) {
                JOptionPane.showMessageDialog(null, "输错了,老铁从头再来吧!", "怜悯的眼神看着你", JOptionPane.ERROR_MESSAGE);
                System.exit(0);
                e.printStackTrace();
            }

            try {
                inetAddress = InetAddress.getByName(UsernameOrIp);
            } catch (UnknownHostException ex) {
                ex.printStackTrace();
            }
            if (inetAddress != null) {
                JOptionPane.showMessageDialog(null, "开怼!", "安排上了!", JOptionPane.WARNING_MESSAGE);
                start = true;//设置游戏开始
                if (clientPort == serverPort) {// 若主机名或IP为自己电脑(或不填写)且两个端口一样,则为单机模式
                    danJi = true;//开启单机模式
                } else {
                    danJi = false;// 联机模式,设置单机模式为false
                }
                t.start();
            }
        }

        if (respones == JOptionPane.NO_OPTION || respones == JOptionPane.CLOSED_OPTION) {
            JOptionPane.showMessageDialog(null, "自怼?啥也别说了,老铁双击666", "冷笑不语", JOptionPane.INFORMATION_MESSAGE);
            try {
                inetAddress = InetAddress.getLocalHost();
                serverPort = clientPort = 9653;// 默认端口号为9653
                start = true;//设置游戏开始
                danJi = true;// 开启单机模式
                t.start();//开启线程开启游戏
            } catch (UnknownHostException ex) {
                JOptionPane.showMessageDialog(null, "老铁,本地网络出问题了吧?", "是时候换台电脑了老铁", JOptionPane.ERROR_MESSAGE);
                System.exit(0);
                ex.printStackTrace();
            }
        }
    }

    @Override
    public void run() {// 使用线程,端口号一致可进行单机游戏
        clearChess();//游戏开启,重绘棋盘
        receive();//开启服务端接收数据
    }

    public NetLTWZQDemo() {
        setBackground(Color.gray);
        addMouseListener(this);//注册鼠标事件
        setVisible(true);//设置窗体可见
    }

    public void paintComponent(Graphics g) {
        Graphics2D g2D = (Graphics2D) g;// 使用Graphics2D使构图平滑
        g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        super.paintComponent(g);
        drawChessBoard(g);
        drawChess(g);
    }

    public void drawChess(Graphics g) {
        for (int i = 0; i < 12; i++) {
            for (int j = 0; j < 12; j++) {
                if (chess[i][j] == 1) {
                    g.setColor(Color.black);
                    g.fillOval((i + 1) * 50 - 19, (j + 1) * 50 - 19, 38, 38);
                } else if (chess[i][j] == 2) {
                    g.setColor(Color.white);
                    g.fillOval((i + 1) * 50 - 19, (j + 1) * 50 - 19, 38, 38);
                } else if (chess[i][j] == 11) {// 点击落黑子时外加个黑框
                    g.setColor(Color.black);
                    g.fillOval((i + 1) * 50 - 19, (j + 1) * 50 - 19, 38, 38);
                    g.drawRect((i + 1) * 50 - 19, (j + 1) * 50 - 19, 38, 38);
                } else if (chess[i][j] == 22) {// 点击落白子时棋子外加个白框
                    g.setColor(Color.white);
                    g.fillOval((i + 1) * 50 - 19, (j + 1) * 50 - 19, 38, 38);
                    g.drawRect((i + 1) * 50 - 19, (j + 1) * 50 - 19, 38, 38);
                }
            }
        }
    }

    public void drawChessBoard(Graphics g) {
        // 画棋盘
        for (int i = 50; i <= 600; i += 50) {
            g.setColor(Color.WHITE);
            g.drawLine(i, 50, i, 600);
            g.drawLine(50, i, 600, i);
        }
    }

    public void clearChess() {// 初始化棋盘
        for (int i = 0; i < 12; i++) {
            for (int j = 0; j < 12; j++) {
                chess[i][j] = 0;
            }
        }
        me = true;// 初始化为己方落黑子
        repaint();
    }

    //这个判断胜负的方法是抄的网上一段代码,略作修改(为保持原貌几乎没有修改,提醒自己不能这么干,嗯!),emmm,互相学习嘛,哈哈哈(尬笑),实在是想的脑阔疼,偷个懒...(不过好像点的太快,会出现bug,导致没有5子连着也会判胜,可能是错觉吧...)
    void checkWiner() {// 判断胜方
        int black_count = 0;
        int white_count = 0;
        for (int i = 0; i < 12; i++) {// 横向判断
            for (int j = 0; j < 12; j++) {
                if (chess[i][j] == 1 || chess[i][j] == 11) {
                    black_count++;
                    if (black_count == 5) {
                        JOptionPane.showMessageDialog(this, "黑棋胜利");
                        clearChess();
                        return;
                    }
                } else {
                    black_count = 0;
                }
                if (chess[i][j] == 2 || chess[i][j] == 22) {
                    white_count++;
                    if (white_count == 5) {
                        JOptionPane.showMessageDialog(this, "白棋胜利");
                        clearChess();
                        return;
                    }
                } else {
                    white_count = 0;
                }
            }
        }
        for (int i = 0; i < 12; i++) {// 竖向判断
            for (int j = 0; j < 12; j++) {
                if (chess[j][i] == 1 || chess[j][i] == 11) {
                    black_count++;
                    if (black_count == 5) {
                        JOptionPane.showMessageDialog(this, "黑棋胜利");
                        clearChess();
                        return;
                    }
                } else {
                    black_count = 0;
                }
                if (chess[j][i] == 2 || chess[j][i] == 22) {
                    white_count++;
                    if (white_count == 5) {
                        JOptionPane.showMessageDialog(this, "白棋胜利");
                        clearChess();
                        return;
                    }
                } else {
                    white_count = 0;
                }
            }
        }
        for (int i = 0; i < 7; i++) {// 左向右斜判断
            for (int j = 0; j < 7; j++) {
                for (int k = 0; k < 5; k++) {
                    if (chess[i + k][j + k] == 1 || chess[i + k][j + k] == 11) {
                        black_count++;
                        if (black_count == 5) {
                            JOptionPane.showMessageDialog(this, "黑棋胜利");
                            clearChess();
                            return;
                        }
                    } else {
                        black_count = 0;
                    }
                    if (chess[i + k][j + k] == 2 || chess[i + k][j + k] == 22) {
                        white_count++;
                        if (white_count == 5) {
                            JOptionPane.showMessageDialog(this, "白棋胜利");
                            clearChess();
                            return;
                        }
                    } else {
                        white_count = 0;
                    }
                }
            }
        }
        for (int i = 4; i < 12; i++) {// 右向左斜判断 11->12
            for (int j = 6; j >= 0; j--) {
                for (int k = 0; k < 5; k++) {
                    if (chess[i - k][j + k] == 1 || chess[i - k][j + k] == 11) {
                        black_count++;
                        if (black_count == 5) {
                            JOptionPane.showMessageDialog(this, "黑棋胜利");
                            clearChess();
                            return;
                        }
                    } else {
                        black_count = 0;
                    }
                    if (chess[i - k][j + k] == 2 || chess[i - k][j + k] == 22) {
                        white_count++;
                        if (white_count == 5) {
                            JOptionPane.showMessageDialog(this, "白棋胜利");
                            clearChess();
                            return;
                        }
                    } else {
                        white_count = 0;
                    }
                }
            }
        }
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        System.out.println(e.getX() + "==" + e.getY());// 在棋盘上点击鼠标时在控制台输出坐标信息,方便测试
        if (start == false) {
            return;
        }
        ex = e.getX();
        ey = e.getY();
        if (ex >= 25 && ex <= 625 && ey >= 25 && ey <= 625) {// 控制鼠标点击位于棋盘内部
            x = (ex - 19) / 50;// 使棋子排列在棋盘交点处
            y = (ey - 19) / 50;
        }
        if (chess[x][y] != 0) {// 当点击位置已经有棋子,则此次点击无效,并继续落子
            return;
        }
        if (one == true) {// 轮到一方落子时,落子判断胜负,并传送鼠标点击的坐标
            doit();// 若传送坐标前判断胜负,则会导致最后一颗棋子落子信息阻塞,判断完毕后再次传送,故此处不判断
            sendXY(ex, ey);// 先传送坐标再判断胜负
            if (danJi == false) {// 联机模式下才进行此次胜负判断
                checkWiner();
            }
        }
        one = false;// 换到对方落子
    }

    private void doit() {// 判断落子颜色及判断胜方
        x = (ex - 19) / 50;
        y = (ey - 19) / 50;
        if (chess[x][y] == 0) {// 点击位置无棋子
            if (me == true) {
                chess[x][y] = 11;// 黑子
                for (int i = 0; i < 12; i++) {
                    for (int j = 0; j < 12; j++) {
                        if (chess[i][j] == 22) {
                            chess[i][j] = 2;// 把刚才下的加白框的白子转换为普通白子
                        }
                    }
                }
                me = false;// 换为白子落子
            } else if (me == false) {
                chess[x][y] = 22;// 白子
                for (int i = 0; i < 12; i++) {
                    for (int j = 0; j < 12; j++) {
                        if (chess[i][j] == 11) {
                            chess[i][j] = 1;// 把刚才下的加黑框的黑子转换为普通黑子
                        }
                    }
                }
                me = true;// 换为黑子落子
            }
        }
        repaint();// 重绘棋盘后判断胜方
    }

    public void sendXY(int xSend, int ySend) {// 传送鼠标点击的坐标
        try {// 问题在于每次传送坐标都要重新创建一个socket对象,不知道怎么改进
            Socket socket = new Socket(inetAddress, serverPort);// 设置对方服务器端IP,并设置端口号为9653;
            OutputStream os = socket.getOutputStream();
            String strr = xSend + "-" + ySend;// 使用-符号连接坐标,并以字符串形式发送到服务端
            os.write(strr.getBytes());
            socket.shutdownOutput();// 及时发送位置信息,不用closs()是保持通信一直连接
        } catch (Exception ex) {
            JOptionPane.showMessageDialog(null, "老铁,多人一起怼不合适吧?", "怼亦有道!", JOptionPane.ERROR_MESSAGE);
            System.exit(0);
            ex.printStackTrace();
        }
    }

    public void receive() {// 服务端接收对方发来的坐标
        try {
            ServerSocket serverSocket = new ServerSocket(clientPort);// 从该端口接收数据
            while (true) {
                Socket sSocket = serverSocket.accept();
                InputStream is = sSocket.getInputStream();
                byte[] bys = new byte[1024];
                int len = is.read(bys);
                String strReceive = new String(bys, 0, len);
                System.out.println(strReceive);//接收到的字符串输出在控制台,方便测试
                String[] strs = strReceive.split("-");// 使用"-"分割字符串,得到坐标
                ex = Integer.parseInt(strs[0]);
                ey = Integer.parseInt(strs[1]);
                doit();// 得到坐标后,重绘棋盘并判断胜方
                checkWiner();
                one = true;// 己方不能落子,棋权交给对方
            }
        } catch (IOException e) {
            JOptionPane.showMessageDialog(null, "老铁,多人一起怼不合适吧?", "怼亦有道!", JOptionPane.ERROR_MESSAGE);
            System.exit(0);
            e.printStackTrace();
        }
    }

    @Override
    public void mouseClicked(MouseEvent e) {
    }

    @Override
    public void mousePressed(MouseEvent e) {
    }

    @Override
    public void mouseEntered(MouseEvent e) {
    }

    @Override
    public void mouseExited(MouseEvent e) {
    }
}

